/**
 * Copyright (c) 2012-2014, Yang Yang <cntototo@gmail.com>
 *
 * This software may be distributed under the terms of the New BSD License.
 * See README for more details.
 */

#include "util.h"

#define LOG_TAG "UTIL"

#include "include.h"
#include "def.h"
#include "log.h"
#include "os.h"
#include "ieee802_11_defs.h"

#define IE_ELEM_EID_L 1
#define IE_ELEM_LEN_L 1

u8 zero_addr[ETH_ALEN] = {0x0};

/***********************************************************
 * Local functions
 ***********************************************************
 */
static char *build_resp(char *fill)
{
	int len = strlen(fill) + 1;
	char *resp = (char *) zalloc(len);
	if (!resp) {
		wfs_printf(LOG_ERR, "%s", err_to_str(WFS_INTERNAL_ERR_OOM));
		return NULL;
	}

	strncpy(resp, fill, len);
	resp[len - 1] = 0;

	return resp;
}


static void print_encode(char *dst, u32 dst_len, const u8 *src, u32 src_len)
{
	char *end = dst + dst_len;
	u32 i = 0;

	assert(dst && dst_len && src && src_len);

	for (i = 0; i < src_len; ++i) {
		if (dst + 4 > end)
			break;
		switch (src[i]) {
		case '\"':
			*dst++ = '\\';
			*dst++ = '\"';
			break;
		case '\\':
			*dst++ = '\\';
			*dst++ = '\\';
			break;
		case '\e':
			*dst++ = '\\';
			*dst++ = 'e';
			break;
		case '\n':
			*dst++ = '\\';
			*dst++ = 'n';
			break;
		case '\r':
			*dst++ = '\\';
			*dst++ = 'r';
			break;
		case '\t':
			*dst++ = '\\';
			*dst++ = 't';
			break;
		default:
			if (32 <= src[i] && src[i] <= 127) {
				*dst++ = src[i];
			} else {
				int res = snprintf(dst, end - dst, "\\x%02x",
						src[i]);
				if (0 < res && res < end - dst)
					dst += res;
			}
			break;
		}
	}

	*dst = '\0';
}


static void print_u32(char *dst, u32 dst_len, const u32 *src, u32 src_len,
		const char* delim)
{
	char *end = dst + dst_len;
	u32 i = 0;
	int res = 0;

	assert(dst && dst_len && src && src_len);

	for (i = 0; i < src_len; ++i) {
		if (dst >= end)
			break;
		res = snprintf(dst, end - dst, "%u%s", src[i], delim);
		if (res < 0 || res >= end -dst)
			break;
		dst += res;
	}

	/** Remove the last delim */
	*(dst - strlen(delim)) = '\0';
}


static int hexchar2num(char c)
{
	if ('0' <= c && c <= '9')
		return c - '0';
	if ('a' <= c && c <= 'f')
		return c - 'a' + 10;
	if ('A' <= c && c <= 'F')
		return c - 'A' + 10;
	return -1;
}


static int hexstr2byte(const char *hex)
{
	int a = 0, b = 0;
	a = hexchar2num(*hex++);
	if (a < 0)
		return -1;
	b = hexchar2num(*hex);
	if (b < 0)
		return -1;
	return (a << 4) | b;
}


/***********************************************************
 * Glocal functions
 ***********************************************************
 */
char *build_ok_resp(void)
{
	return build_resp(OK_STR);
}


char *build_fail_resp(void)
{
	return build_resp(FAIL_STR);
}


u32 cnt_chr_in_str(const char *str, const char chr)
{
	u32 ret = 0;
	char *pos = NULL;

	pos = strchr(str, chr);
	while (pos) {
		++ret;
		pos = strchr(pos + 1, chr);
	}

	return ret;
}


const char *err_to_str(enum wfs_internal_err err)
{
	switch (err) {
	case WFS_INTERNAL_ERR_SUCCESS:
		return "Success.";
	case WFS_INTERNAL_ERR_INVAL:
		return "The input info is invalid.";
	case WFS_INTERNAL_ERR_OOM:
		return "Out of memory.";
	case WFS_INTERNAL_ERR_OS:
		return "Error happens when communicating with OS.";
	case WFS_INTERNAL_ERR_BUSY:
		return "WFS is too busy to perform the operation.";
	case WFS_INTERNAL_ERR_NOTSUPP:
		return "The behavior is not supported at current status.";
	default:
		break;
	}

	return "Unknown error";
}


void free_assoc_params(struct wfs_driver_assoc_params *params)
{
	if (!params)
		return;

	FREE(params->ie);
	FREE(params->ft_ie);
	FREE(params);
}


void free_hw_info(struct wfs_driver_hw_info *hw_info, u32 num)
{
	u32 i = 0;

	if (!hw_info)
		return;

	for (i = 0; i < num; ++i) {
		FREE(hw_info[i].channels);
		FREE(hw_info[i].rates);
	}
	FREE(hw_info);
}


void free_scan_params(struct wfs_driver_scan_params *params)
{
	if (!params)
		return;

	FREE(params->match_devs);
	FREE(params->freqs);
	FREE(params->ie);
	FREE(params);
}


void free_scan_result(struct wfs_driver_scan_result *result)
{
	if (!result)
		return;
	FREE(result->ie);
	FREE(result->beacon_ie);
	FREE(result);
}


void free_scan_results(struct wfs_driver_scan_results *results)
{
	u32 i = 0;

	if (!results)
		return;

	for (i = 0; i < results->num; ++i)
		free_scan_result(results->table[i]);
	os_free_time(&results->fetch_time);
	FREE(results);
}


enum wfs_driver_hw_mode
freq_to_chan(u32 freq, u8 *chan)
{
	enum wfs_driver_hw_mode mode = WFS_DRV_HW_MODE_UNKNOWN;

	if (freq >= 2412 && freq <= 2472) {
		mode = WFS_DRV_HW_MODE_IEEE80211G;
		*chan = (freq - 2407) / 5;
	} else if (freq == 2484) {
		mode = WFS_DRV_HW_MODE_IEEE80211B;
		*chan = 14;
	} else if (freq >= 4900 && freq < 5000) {
		mode = WFS_DRV_HW_MODE_IEEE80211A;
		*chan = (freq - 4000) / 5;
	} else if (freq >= 5000 && freq < 5900) {
		mode = WFS_DRV_HW_MODE_IEEE80211A;
		*chan = (freq - 5000) / 5;
	} else if (freq >= 56160 + 2160 * 1 && freq <= 56160 + 2160 * 4) {
		mode = WFS_DRV_HW_MODE_IEEE80211AD;
		*chan = (freq - 56160) / 2160;
	}

	return mode;
}


const struct wfs_driver_scan_result *
get_bss_by_bssid(const struct wfs_driver_scan_results *results, const u8 *bssid)
{
	u32 i = 0;

	if (!results || !bssid || is_zero_mac_addr(bssid))
		return NULL;

	for (i = 0; i < results->num; ++i) {
		if (is_same_mac(bssid, results->table[i]->bssid))
			return results->table[i];
	}

	return NULL;
}


const struct wfs_driver_scan_result *
get_bss_by_ssid(const struct wfs_driver_scan_results *results,
		const u8 *ssid, u32 ssid_len)
{
	u32 i = 0;

	if (!results || !ssid || ssid_len == 0)
		return NULL;

	for (i = 0; i < results->num; ++i) {
		if (memcmp(ssid, results->table[i]->ssid, ssid_len) == 0)
			return results->table[i];
	}

	return NULL;
}


int get_akm_suite(enum wfs_key_mgmt mgmt)
{
	int ret = 0;
	switch (mgmt) {
	case KEY_MGMT_CCKM:
		ret = WLAN_AKM_SUITE_CCKM;
		break;
	case KEY_MGMT_IEEE8021X:
		ret = WLAN_AKM_SUITE_8021X;
		break;
	case KEY_MGMT_IEEE8021X_FT:
		ret = WLAN_AKM_SUITE_FT_8021X;
		break;
	case KEY_MGMT_PSK_FT:
		ret = WLAN_AKM_SUITE_FT_PSK;
		break;
	case KEY_MGMT_PSK:
		ret = WLAN_AKM_SUITE_PSK;
		break;
	default:
		wfs_printf(LOG_ERR, "%d is not for AKM suite", mgmt);
		break;
	}

	return ret;
}


const char *get_ch_width_str(enum wfs_ch_width width)
{
	switch (width) {
	case CH_WIDTH_5:
		return "5MHz";
	case CH_WIDTH_10:
		return "10MHz";
	case CH_WIDTH_20_NOHT:
		return "20MHz NOHT";
	case CH_WIDTH_20:
		return "20MHz";
	case CH_WIDTH_40:
		return "40MHz";
	case CH_WIDTH_80:
		return "80MHz";
	case CH_WIDTH_80P80:
		return "80P80Mhz";
	case CH_WIDTH_160:
		return "160MHz";
	case CH_WIDTH_UNKNOWN:
	default:
		return "UNKNOWN";
	}
}


int get_cipher_suite(enum wfs_cipher cipher)
{
	int ret = 0;

	switch (cipher) {
	case CIPHER_WEP40:
		ret = WLAN_CIPHER_SUITE_WEP40;
		break;
	case CIPHER_WEP104:
		ret = WLAN_CIPHER_SUITE_WEP104;
		break;
	case CIPHER_TKIP:
		ret = WLAN_CIPHER_SUITE_TKIP;
		break;
	case CIPHER_CCMP:
		ret = WLAN_CIPHER_SUITE_CCMP;
		break;
	case CIPHER_GCMP:
		ret = WLAN_CIPHER_SUITE_GCMP;
		break;
	case CIPHER_CCMP_256:
		ret = WLAN_CIPHER_SUITE_CCMP_256;
		break;
	case CIPHER_GCMP_256:
		ret = WLAN_CIPHER_SUITE_GCMP_256;
		break;
	case CIPHER_SMS4:
		ret = WLAN_CIPHER_SUITE_SMS4;
		break;
	case CIPHER_KRK:
		ret = WLAN_CIPHER_SUITE_KRK;
		break;
	case CIPHER_BIP_GMAC_128:
		ret = WLAN_CIPHER_SUITE_BIP_GMAC_128;
		break;
	case CIPHER_BIP_GMAC_256:
		ret = WLAN_CIPHER_SUITE_BIP_GMAC_256;
		break;
	case CIPHER_BIP_CMAC_256:
		ret = WLAN_CIPHER_SUITE_BIP_CMAC_256;
		break;
	case CIPHER_IGTK:
		ret = WLAN_CIPHER_SUITE_AES_CMAC;
		break;
	default:
		wfs_printf(LOG_ERR, "Unsupported cipher %d", cipher);
		break;
	}

	return ret;
}


int get_cipher_suites(enum wfs_cipher ciphers, u32 *suites, int max_suites)
{
	int num = 0;

	if (num < max_suites && (ciphers & CIPHER_WEP40))
		suites[num++] = WLAN_CIPHER_SUITE_WEP40;
	if (num < max_suites && (ciphers & CIPHER_WEP104))
		suites[num++] = WLAN_CIPHER_SUITE_WEP104;
	if (num < max_suites && (ciphers & CIPHER_TKIP))
		suites[num++] = WLAN_CIPHER_SUITE_TKIP;
	if (num < max_suites && (ciphers & CIPHER_CCMP))
		suites[num++] = WLAN_CIPHER_SUITE_CCMP;
	if (num < max_suites && (ciphers & CIPHER_GCMP))
		suites[num++] = WLAN_CIPHER_SUITE_GCMP;
	if (num < max_suites && (ciphers & CIPHER_CCMP_256))
		suites[num++] = WLAN_CIPHER_SUITE_CCMP_256;
	if (num < max_suites && (ciphers & CIPHER_GCMP_256))
		suites[num++] = WLAN_CIPHER_SUITE_GCMP_256;

	return num;
}


const u8 *get_ie_elem(const u8 *ie, u32 ie_len, u8 eid)
{
	const u8 *pos = NULL, *end = NULL;

	assert(ie);

	pos = ie;
	end = ie + ie_len;
	while (pos + IE_ELEM_EID_L < end) {
		const u8 *next_elem = pos + IE_ELEM_EID_L + pos[IE_ELEM_EID_L]
				+ IE_ELEM_LEN_L;
		if (next_elem > end)
			break;
		if (pos[0] == eid)
			return pos;
		pos = next_elem;
	}

	return NULL;
}


u32 get_ie_max_rate(const u8 *ie, u32 ie_len)
{
	u32 rate = 0, i = 0;
	const u8 *pos = NULL;

	assert(ie);

	pos = get_ie_elem(ie, ie_len, WLAN_EID_SUPP_RATES);
	for (i = 0; pos && i < pos[IE_ELEM_EID_L]; ++i) {
		u32 cur_rate = pos[IE_ELEM_EID_L + IE_ELEM_LEN_L + i] & 0x7f;
		rate = MAX(rate, cur_rate);
	}

	pos = get_ie_elem(ie, ie_len, WLAN_EID_EXT_SUPP_RATES);
	for (i = 0; pos && i < pos[IE_ELEM_EID_L]; ++i) {
		u32 cur_rate = pos[IE_ELEM_EID_L + IE_ELEM_LEN_L + i] & 0x7f;
		rate = MAX(rate, cur_rate);
	}

	return rate;
}


const u8 *get_ie_ssid(const u8 *ie, u32 ie_len, u32 *ssid_len)
{
	const u8 *pos = NULL;

	assert(ie && ssid_len);

	pos = get_ie_elem(ie, ie_len, WLAN_EID_SSID);
	if (!pos)
		return NULL;

	*ssid_len = pos[1];
	return pos + 2;
}


const u8 *get_ie_vendor(const u8 *ie, u32 ie_len, u32 vendor_type)
{
	const u8 *pos = NULL, *end = NULL;

	assert(ie);

	pos = ie;
	end = ie + ie_len;
	while (pos + IE_ELEM_EID_L < end) {
		const u8 *next_elem = pos + IE_ELEM_EID_L + pos[IE_ELEM_EID_L]
				+ IE_ELEM_LEN_L;
		if (next_elem > end)
			break;
		if (pos[0] == WLAN_EID_VENDOR_SPECIFIC && pos[1] >= 4
				&& vendor_type == get_be32(&pos[2]))
			return pos;
		pos = next_elem;
	}

	return NULL;
}


const char *get_ssid_str(const u8 *ssid, u32 len)
{
	static char ret[MAX_SSID_LEN * 4 + 1];

	if (!ssid)
		ret[0] = '\0';
	else
		print_encode(ret, sizeof(ret), ssid, len);

	return ret;
}


const char *get_tdls_oper_str(enum wfs_tdls_oper oper)
{
	switch (oper) {
	case TDLS_DISC_REQ:
		return "DISCOVERY_REQUEST";
	case TDLS_SETUP:
		return "SETUP";
	case TDLS_TEARDOWN:
		return "TEARDOWN";
	case TDLS_ENABLE_LINK:
		return "ENABLE_LINK";
	case TDLS_DISABLE_LINK:
		return "DISABLE_LINK";
	case TDLS_UNKNOWN:
	default:
		return "UNKNOWN";
	}
}


const char *get_u32_array_str(const u32 *arr, u32 len, const char *delim)
{
	static char ret[BUF_LEN];

	if (!arr)
		ret[0] = '\0';
	else
		print_u32(ret, sizeof(ret), arr, len, delim);

	return ret;
}


enum wfs_internal_err
hexstr2bin(const char *hex, u8 *buf, u32 len)
{
	int res = 0;
	u32 i = 0;

	for (i = 0; i < len; ++i) {
		res = hexstr2byte(hex);
		if (res < 0)
			return WFS_INTERNAL_ERR_INVAL;
		*buf++ = res;
		hex += 2;
	}

	return WFS_INTERNAL_ERR_SUCCESS;
}


void *mem_dup(const void *src, u32 len)
{
	void *ret = NULL;

	if (!src || len == 0)
		return NULL;

	ret = zalloc(len);
	if (!ret) {
		wfs_perror("zalloc");
		return NULL;
	}
	memcpy(ret, src, len);

	return ret;
}


char *str_strip(char *s)
{
	u32 len = 0;
	char *end = NULL;

	if (!s)
		return s;

	len = strlen(s);
	if (!len)
		return s;

	end = s + len - 1;
	while (end >= s && isspace(*end))
		--end;
	*(end + 1) = '\0';

	while (*s && isspace(*s))
		++s;

	return s;
}
